/***
* ==++==
*
* Copyright (c) Microsoft Corporation.  All rights reserved.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* azurestorage.h
*
* This file contains azure storage classes
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/

#pragma once

#include <string>
#include <vector>
#include <memory>
#include <unordered_map>

#include "azurestoragedefs.h"
#include "pplxtasks.h"
#include "uri.h"
#include "asyncrt_utils.h"
#include "http_client.h"

namespace azure { namespace storage {

/// <summary>
/// Association of a storage account name and its public key
/// </summary>
class storage_credentials
{
public:
    storage_credentials()
    {
    }

    /// <summary>
    /// Creates storage credentials based on a storage account name and an azure key
    /// </summary>
    /// <param name="accountName">Name of an existing azure storage account.</param>
    /// <param name="key">key to the account that is base64 encoded.</param>
    _AZURESTORAGEIMP storage_credentials(const std::string& accountName, const std::string& key);

    /// <summary>
    /// Returns the account name.
    /// </summary>
    /// <returns>The name of the storage account</returns>
    const std::string& get_account_name() const 
    { 
        return m_accountName; 
    }

    /// <summary>
    /// Returns a reference to the non-base64 encoded key.
    /// </summary>
    /// <returns>The non-encoded key for this storage account</returns>
    const std::vector<unsigned char>& get_key() const
    { 
        return m_key; 
    }

    /// <summary>
    /// Returns a copy of the base64 encoded key.
    /// </summary>
    /// <returns>The base64 encoded key for this storage account</returns>
    _AZURESTORAGEIMP std::string get_base64_encoded_key() const;

private:
    std::string m_accountName;
    std::vector<unsigned char> m_key;
};

/// <summary>
/// Represents the local development storage account
/// </summary>
class local_storage
{
public:

    /// <summary>
    /// Creates storage credentials for the local development storage account.
    /// </summary>
    /// <returns>The credentials for the local development storage account</returns>
    static inline storage_credentials get_credentials()
    {
        return storage_credentials("devstoreaccount1", "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==");
    }

    /// <summary>
    /// Returns the local stroage endpoint URI for blobs
    /// </summary>
    /// <returns>The URI representing the storage endpoint</returns>
    static inline http::uri get_blob_uri()
    {
        auto uri = http::uri("http://127.0.0.1:10000");
        return uri.append_path(get_credentials().get_account_name());
    }

    /// <summary>
    /// Returns the local stroage endpoint URI for queues
    /// </summary>
    /// <returns>The URI representing the storage endpoint</returns>
    static inline http::uri get_queue_uri()
    {
        auto uri = http::uri("http://127.0.0.1:10001");
        return uri.append_path(get_credentials().get_account_name());
    }

    /// <summary>
    /// Returns the local stroage endpoint URI for table
    /// </summary>
    /// <returns>The URI representing the storage endpoint</returns>
    static inline http::uri get_table_uri()
    {
        auto uri = http::uri("http://127.0.0.1:10002");
        return uri.append_path(get_credentials().get_account_name());
    }
};

/// <summary>
/// Represents an opaque continuation marker that is used to mark the current position
/// from where subsequent operations should resume from.
/// </summary>
class continuation_token
{
public:

    /// <summary>
    /// Returns an empty continuation token.
    /// </summary>
    /// <returns>An empty continuation token</returns>
    static inline continuation_token none()
    {
        return continuation_token();
    }

    /// <summary>
    /// Constructs an empty continuation token.
    /// </summary>
    continuation_token()
    {
    }

    /// <summary>
    /// Returns a continuation token for the given marker.
    /// </summary>
    /// <param name="marker">continuation marker</param>
    continuation_token(const std::wstring& marker)
        : m_marker(marker)
    {
    }

    /// <summary>
    /// Determines if this represents an empty marker
    /// </summary>
    /// <returns>True if there is no marker, false otherwise</returns>
    bool is_none()
    {
        return m_marker.empty();
    }

    /// <summary>
    /// Returns the marker
    /// </summary>
    /// <returns>An empty continuation token</returns>
    const std::wstring& get_marker()
    {
        return m_marker;
    }

private:

    std::wstring m_marker;
};

/// <summary>
/// Represents a segment of the result of an operation.
/// </summary>
template<typename result_type>
class result_segment
{
public:

    /// <summary>
    /// Constructs a result segment.
    /// </summary>
    result_segment()
    {
    }

    /// <summary>
    /// Constructs a result segment.
    /// </summary>
    result_segment(std::vector<result_type> results, continuation_token token)
        : m_results(results), m_token(token)
    {
    }

    /// <summary>
    /// Returns the results in this segment.
    /// </summary>
    /// <returns>A vector of results</returns>
    const std::vector<result_type>& get_result() const
    {
        return m_results;
    }

    /// <summary>
    /// Returns the continuation marker for obtaining the next result segment
    /// </summary>
    /// <returns>The continuation token</returns>
    const continuation_token& get_continuation_token() const
    {
        return m_token;
    }

private:

    std::vector<result_type> m_results;
    continuation_token m_token;
};

/// <summary>
/// Represents an azure storage operation exception
/// </summary>
class storage_exception : public std::exception
{
public:

    explicit storage_exception(unsigned long error)
        : m_error(error)
    {
    }

    storage_exception(unsigned long error, const std::string& message)
        : m_error(error), m_errorMessage(message)
    { }

    storage_exception(unsigned long error, const std::string& message, const std::string& storageErrCode, const std::string& details)
        : m_error(error), m_errorMessage(message), m_storageError(storageErrCode), m_details(details)
    { }

    virtual const char *what() const
    {
        return m_errorMessage.c_str();
    }

    unsigned long error_code()
    {
        return m_error;
    }

    const std::string& storage_error_code()
    {
        return m_storageError;
    }

    const std::string& storage_error_details()
    {
        return m_details;
    }

private:
    unsigned long m_error;
    std::string m_storageError;
    std::string m_errorMessage;
    std::string m_details;
};

/// <summary>
/// Azure storage specific logging support.
/// </summary>
namespace logging
{
    /// <summary>
    /// Azure storage logging modes.
    /// </summary>
    enum mode
    {
        log_info  = 0x1,
        log_warn  = 0x2,
        log_error = 0x4,
        log_fatal = 0x8,
    };

    /// <summary>
    /// Enables Azure storage logging
    /// </summary>
    /// <param name="contents">The logging mode to enable</param>
    _AZURESTORAGEIMP void __cdecl enable(int mode = -1);

    /// <summary>
    /// Disables Azure storage logging
    /// </summary>
    _AZURESTORAGEIMP void __cdecl disable();

} // namespace logging

// QUEUE 
#pragma region azure queue service

namespace details
{
    struct message_data
    {
        message_data() : m_dequeueCount(0)
        {
        }

        message_data(const std::string& contents) : m_contents(contents.begin(), contents.end()), m_dequeueCount(0)
        {
        }

        message_data(const std::vector<unsigned char>& contents) : m_contents(contents), m_dequeueCount(0)
        {
        }

        message_data(std::vector<unsigned char>&& contents) : m_contents(contents), m_dequeueCount(0)
        {
        }

        std::vector<unsigned char> m_contents;
        std::wstring m_id;
        std::wstring m_popReceipt;
        utilities::datetime m_insertTime;
        utilities::datetime m_expireTime;
        utilities::datetime m_nextVisibleTime;
        int m_dequeueCount;
    };
} //namespace details

/// <summary>
/// Represents a message that could be used with an Azure queue
///</summary>
class cloud_queue_message
{
public:

    cloud_queue_message()
        : m_data(std::make_shared<details::message_data>())
    {
    }

    /// <summary>
    /// Creates a cloud queue message
    ///</summary>
    /// <param name="contents">The message contents</param>
    cloud_queue_message(const std::string& contents)
        : m_data(std::make_shared<details::message_data>(contents))
    {
    }

    /// <summary>
    /// Creates a cloud queue message
    ///</summary>
    /// <param name="contents">The message contents</param>
    cloud_queue_message(const std::vector<unsigned char>& contents)
        : m_data(std::make_shared<details::message_data>(contents))
    {
    }

    /// <summary>
    /// Creates a cloud queue message
    ///</summary>
    /// <param name="contents">The message contents</param>
    cloud_queue_message(std::vector<unsigned char>&& contents)
        : m_data(std::make_shared<details::message_data>(std::move(contents)))
    {
    }

    /// <summary>
    /// Returns the unique id of the message
    ///</summary>
    /// <returns>The message id</returns>
    std::string get_id() const
    {
        return utilities::conversions::utf16_to_utf8(m_data->m_id);
    }

    /// <summary>
    /// Returns the message contents as a string
    ///</summary>
    /// <returns>The message contents</returns>
    const std::vector<unsigned char>& contents() const
    {
        return m_data->m_contents;
    }

    /// <summary>
    /// Returns the dequeue count indicating the number of times the
    /// the message has been retrieved from the queue
    ///</summary>
    /// <returns>The dequeue count</returns>
    int dequeue_count() const
    {
        return m_data->m_dequeueCount;
    }

    /// <summary>
    /// Returns the expiration time for the message. (insertion time + time to live)
    ///</summary>
    /// <returns>The expiration time for the message</returns>
    utilities::datetime expiration_time() const
    {
        return m_data->m_expireTime;
    }

    /// <summary>
    /// Returns the insertion time of the message.
    ///</summary>
    /// <returns>The insertion time of the message</returns>
    utilities::datetime insertion_time() const
    {
        return m_data->m_insertTime;
    }

    /// <summary>
    /// Returns the next visibility time (dequeue time + visibility timeout)
    ///</summary>
    /// <returns>The next time when the message would again be visible</returns>
    utilities::datetime next_visibility_time() const
    {
        return m_data->m_nextVisibleTime;
    }

    // internal
    details::message_data * get_data() const
    {
        return m_data.get();
    }

    static const size_t max_message_size = 64 * 1024 * 1024; // 64 KB

private:
   
    typedef std::shared_ptr<details::message_data> dataptr;

    dataptr m_data;
};

/// <summary>
/// Represents the user meta-data for queues
///</summary>
typedef std::unordered_map<std::wstring, std::wstring> cloud_queue_metadata;

// forward declaration
class cloud_queue;

/// <summary>
/// Represents the Azure storage queue service
///</summary>
class cloud_queue_client
{
public:

    cloud_queue_client()
    {
    }

    /// <summary>
    /// Creates a client for the given queue endpoint.
    ///</summary>
    /// <param name="baseUri">URI for the queue service endpoint for the given account</param>
    /// <param name="credentials">Storage account credentials</param>
    cloud_queue_client(const http::uri& baseUri, const storage_credentials& credentials)
        : m_baseUri(baseUri), m_credentials(credentials)
    {
    }

    /// <summary>
    /// Returns the queue service endpoint
    ///</summary>
    /// <returns>The URI for the queue service</returns>
    http::uri get_uri() const
    {
        return m_baseUri;
    }

    /// <summary>
    /// Returns the queue service credentials
    ///</summary>
    /// <returns>The storage credentials for the queue service</returns>
    const storage_credentials& get_credentials()
    {
        return m_credentials;
    }

    /// <summary>
    /// Returns an existing queue or create a new one if one doesn't exist
    ///</summary>
    /// <param name="queueName">The queue resource name</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<cloud_queue> create_queue(const std::string& queueName)
    {
        return create_queue(queueName, cloud_queue_metadata());
    }

    /// <summary>
    /// Returns an existing queue or create a new one if one doesn't exist
    ///</summary>
    /// <param name="queueName">The queue resource name</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<cloud_queue> create_queue(const std::wstring& queueName)
    {
        return create_queue(queueName, cloud_queue_metadata());
    }

    /// <summary>
    /// Returns an existing queue or create a new one if one doesn't exist
    ///</summary>
    /// <param name="queueName">The queue resource name</param>
    /// <param name="metadata">The queue metadata</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<cloud_queue> create_queue(const std::string& queueName, const cloud_queue_metadata& metadata)
    {
        return create_queue(utilities::conversions::utf8_to_utf16(queueName), metadata);
    }

    /// <summary>
    /// Returns an existing queue or create a new one if one doesn't exist
    ///</summary>
    /// <param name="queueName">The queue resource name</param>
    /// <param name="metadata">The queue metadata</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<cloud_queue> create_queue(const std::wstring& queueName, const cloud_queue_metadata& metadata);

    /// <summary>
    /// Returns an existing queue. If the queue doesn't exist the task throws an exception
    ///</summary>
    /// <param name="queueName">The queue resource name</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<cloud_queue> get_queue(const std::string& queueName)
    {
        return get_queue(utilities::conversions::utf8_to_utf16(queueName));
    }

    /// <summary>
    /// Returns an existing queue. If the queue doesn't exist the task throws an exception
    ///</summary>
    /// <param name="queueName">The queue resource name</param>
    /// <returns>A task for the cloud queue</returns>
    _AZURESTORAGEIMP pplx::task<cloud_queue> get_queue(const std::wstring& queueName);

    /// <summary>
    /// Deletes the given azure queue
    ///</summary>
    /// <param name="queue">The queue to be deleted</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<void> remove_queue(const std::string& queueName)
    {
        return remove_queue(utilities::conversions::utf8_to_utf16(queueName));
    }

    /// <summary>
    /// Deletes the given azure queue
    ///</summary>
    /// <param name="queue">The queue to be deleted</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<void> remove_queue(const std::wstring& queueName);

    /// <summary>
    /// Returns a list of queues that matches the given prefix
    ///</summary>
    /// <param name="numResults">The number of queues to return.</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<result_segment<cloud_queue>> get_queues(int numResults = 1)
    {
        return get_queues(L"", numResults);
    }

    /// <summary>
    /// Returns a list of queues that matches the given prefix
    ///</summary>
    /// <param name="queuePrefix">The queue resource name prefix.</param>
    /// <param name="numResults">The number of queues to return.</param>
    /// <param name="token">The marker from where to commence this operation.</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<result_segment<cloud_queue>> get_queues(std::wstring prefix, int numResults = 1, continuation_token token = continuation_token::none());

private:

    http::uri           m_baseUri;
    storage_credentials m_credentials;
};

/// <summary>
/// Represents an instance of the Azure queue
///</summary>
class cloud_queue
{
public:

    cloud_queue()
    {
    }

    /// <summary>
    /// Creates an instance of an Azure queue
    ///</summary>
    /// <param name="baseUri">URI for the queue service endpoint for the given account</param>
    /// <param name="credentials">Storage credentials</param>
    cloud_queue(const std::wstring& queueName, const cloud_queue_client& queueClient)
        : m_queueName(queueName), m_queueClient(queueClient)
    {
    }

    /// <summary>
    /// Returns the name of the queue
    ///</summary>
    /// <returns>Name of the queue</returns>
    const std::wstring& get_name() const
    {
        return m_queueName;
    }

    /// <summary>
    /// Returns the queue URI
    ///</summary>
    /// <returns>URI for this queue</returns>
    http::uri get_uri() const
    {
        return m_queueClient.get_uri().append_path(m_queueName);
    }

    /// <summary>
    /// Deletes the queue
    ///</summary>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<void> remove()
    {
        return m_queueClient.remove_queue(m_queueName);
    }

    /// <summary>
    /// Enqueues a message to the back of the queue
    ///</summary>
    /// <param name="msg">The message to be added to the queue</param>
    /// <param name="visibilityTimeout">The time in seconds after which the message would become visible in the queue</param>
    /// <param name="expiryTimeout">The time in seconds for which the message is alive</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<void> add_message(cloud_queue_message msg, int visibilityTimeout = 0, int expiryTimeout = -1);

    /// <summary>
    /// Retrieves a message from the front of the queue
    ///</summary>
    /// <param name="visibilityTimeout">The time in seconds after which the message would become visible again in the queue unless deleted</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<cloud_queue_message> get_message(int visibilityTimeout = 30)
    {
        auto getMessages = get_messages(1, visibilityTimeout);
        return getMessages.then([] (const std::vector<cloud_queue_message>& v) -> cloud_queue_message
        {
            if (v.empty())
            {
                throw storage_exception(http::status_codes::NotFound, "Queue is empty");
            }

            return v[0];
        });
    }

    /// <summary>
    /// Retrieves the given number of message from the front of the queue
    ///</summary>
    /// <param name="count">The number of messages to be retrieved</param>
    /// <param name="visibilityTimeout">The time in seconds after which the message would become visible again in the queue unless deleted</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<std::vector<cloud_queue_message>> get_messages(size_t count = 1, int visibilityTimeout = 30);

    /// <summary>
    /// Retrieves the given number of message from the front of the queue without affecting
    /// the messages visibility
    ///</summary>
    /// <param name="count">The number of messages to be retrieved</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<std::vector<cloud_queue_message>> peek_messages(size_t count = 1);

    /// <summary>
    /// Deletes the given message from the queue
    ///</summary>
    /// <param name="msg">The message to be deleted</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<void> delete_message(cloud_queue_message msg);

    /// <summary>
    /// Deletes all the messages in the queue
    ///</summary>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<void> clear();
    
    /// <summary>
    /// Retrieves the user meta-data for this queue
    ///</summary>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<cloud_queue_metadata> get_metadata();

    /// <summary>
    /// Sets the user meta-data for this queue
    ///</summary>
    /// <param name="metadata">The user metadata to associate with the queue</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<void> set_metadata(const cloud_queue_metadata& metadata);

    /// <summary>
    /// Updates the visibility timeout and the contents of the given message in the queue
    ///</summary>
    /// <param name="msg">The message to be updated</param>
    /// <param name="visibilityTimeout">The updated time in seconds after which the message would become visible again in the queue unless deleted</param>
    /// <returns>A task representing the asynchronous operation</returns>
    pplx::task<cloud_queue_message> update_message(cloud_queue_message msg, int visibilityTimeout)
    {
        return update_message(msg, visibilityTimeout, msg.contents());
    }

    /// <summary>
    /// Updates the visibility timeout and the contents of the given message in the queue
    ///</summary>
    /// <param name="msg">The message to be updated</param>
    /// <param name="visibilityTimeout">The updated time in seconds after which the message would become visible again in the queue unless deleted</param>
    /// <param name="visibilityTimeout">The updated message contents</param>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<cloud_queue_message> update_message(cloud_queue_message msg, int visibilityTimeout, const std::vector<unsigned char>& contents);

    /// <summary>
    /// Retrieves the number of messages in the queue
    ///</summary>
    /// <returns>A task representing the asynchronous operation</returns>
    _AZURESTORAGEIMP pplx::task<size_t> get_size();

private:

    // internal (not exported by the dll)
    http::uri get_queue_resource();
    http::uri get_messages_resource();

private:

    std::wstring m_queueName;
    cloud_queue_client m_queueClient;
};

#pragma endregion

#pragma region Azure Blob Service

typedef std::unordered_map<std::wstring, std::wstring> cloud_blob_metadata;

class cloud_blob;
class cloud_blob_container;

/// <summary>
/// Client class representing a blob storage service account.
/// </summary>
class cloud_blob_client
{
public:
    /// <summary>
    /// Constructor
    /// </summary>
    cloud_blob_client() 
    {
    }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="baseAddress">The blob service endpoint use to create the client.</param>
    /// <param name="credentials">The account credentials</param>
    /// <remarks></remarks>
    cloud_blob_client(const http::uri& baseAddress, const storage_credentials& credentials) 
        : m_baseUri(baseAddress), m_credentials(credentials)
    {
    }

    /// <summary>
    /// Returns the stored credentials.
    /// </summary>
    /// <returns>The credentials for this client's account.</returns>
    const storage_credentials& get_credentials() const { return m_credentials; }

    /// <summary>
    /// Returns the base uri for the blob service.
    /// </summary>
    /// <returns>The base URI for the blob service.</returns>
    const http::uri& get_uri() const { return m_baseUri; }

    /// <summary>
    /// List all the containers in the blob service.
    /// </summary>
    /// <param name="prefix">A container prefix: only containers with names starting with the prefix will be listed</param>
    /// <param name="maxResults">The max number of containers to return</param>
    /// <param name="token">A token used to retrieve the next N containers</param>
    /// <returns>A segment of container list results</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP pplx::task<result_segment<cloud_blob_container>> list_containers(const std::wstring& prefix = L"", size_t maxResults = 5000, continuation_token token = continuation_token::none());

    /// <summary>
    /// Create a new container in the blob service.
    /// </summary>
    /// <param name="name">The name for the container</param>
    /// <returns>A container instance.</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP pplx::task<cloud_blob_container> create_container(const std::wstring& name);

    /// <summary>
    /// Create a new container in the blob service.
    /// </summary>
    /// <param name="name">The name for the container</param>
    /// <param name="metadata">Metadata to use for the container.</param>
    /// <returns>A container instance.</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP pplx::task<cloud_blob_container> create_container(const std::wstring& name, const cloud_blob_metadata& metadata);

private:

    http::uri           m_baseUri;
    storage_credentials m_credentials;
};

/// <summary>
/// Client class representing a blob container.
/// </summary>
class cloud_blob_container
{
public:
    /// <summary>
    /// Constructor
    /// </summary>
    cloud_blob_container() 
    {
    }

    /// <summary>
    /// Get the container name
    /// </summary>
    /// <returns>The name of the container</returns>
    std::wstring get_name() const { return m_name; }

    /// <summary>
    /// Get the full container URI
    /// </summary>
    /// <returns>The URI of the container</returns>
    http::uri get_uri() const { return m_client.get_uri().append_path(m_name); }

    /// <summary>
    /// Get the associated blob storage account client
    /// </summary>
    /// <returns>The container's account client instance</returns>
    const cloud_blob_client& get_client() const { return m_client; }

    /// <summary>
    /// Retrieve any user medatadata that has been defined for the container.
    /// </summary>
    /// <returns>User metadata for the container</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<cloud_blob_metadata> get_metadata();

    /// <summary>
    /// Set user medatadata for the container.
    /// </summary>
    /// <param name="metadata">User metadata for the container</param>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<bool> set_metadata(const cloud_blob_metadata& metadata);

    /// <summary>
    /// Delete the container in the service.
    /// </summary>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<bool> remove();

    /// <summary>
    /// List blobs in the container.
    /// </summary>
    /// <param name="prefix">A blob prefix: only blob with names starting with the prefix will be listed</param>
    /// <param name="maxResults">The max number of blobs to return</param>
    /// <param name="token">A token used to retrieve the next N containers</param>
    /// <returns>A segment of blob list results</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<result_segment<cloud_blob>> list_blobs(const std::wstring& prefix = L"", size_t maxResults = 5000, continuation_token token = continuation_token::none());
    
    /// <summary>
    /// List blobs in the container.
    /// </summary>
    /// <param name="maxResults">The max number of blobs to return</param>
    /// <param name="token">A token used to retrieve the next N containers</param>
    /// <returns>A segment of blob list results</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    pplx::task<result_segment<cloud_blob>> list_blobs(size_t maxResults, continuation_token token = continuation_token::none());

    /// <summary>
    /// Create a local blob instance representing a block blob. Nothing is created in the service as a side-
    /// effect of this call.
    /// </summary>
    /// <param name="name">The name of the blob</param>
    /// <returns>A block blob instance</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP cloud_blob create_block_blob(const std::wstring& name);

    /// <summary>
    /// Create a local blob instance representing a page blob. Nothing is created in the service as a side-
    /// effect of this call.
    /// </summary>
    /// <param name="name">The name of the blob</param>
    /// <returns>A page blob instance</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP cloud_blob create_page_blob(const std::wstring& name);

    /// <summary>
    /// Get an existing blob from the storage container and set its metadata.
    /// </summary>
    /// <param name="name">The name of the blob</param>
    /// <returns>A page blob instance</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP pplx::task<cloud_blob> get_blob(const std::wstring& name);

    /// <summary>
    /// Copy constructor
    /// </summary>
    cloud_blob_container(const cloud_blob_container& other) : m_client(other.m_client), m_name(other.m_name)
    {
    }

    /// <summary>
    /// Create a new container instance.
    /// </summary>
    /// <param name="name">The name for the container</param>
    /// <param name="metadata">Metadata to use for the container.</param>
    /// <remarks>This does not actually create anything in the blob servce account, just a local instance</remarks>
    cloud_blob_container(const std::wstring& name, const cloud_blob_client& client);

private:
    friend class cloud_blob_client;

    /// <summary>
    /// Create the container in the service. If the container already exists, an error is reported.
    /// </summary>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    pplx::task<bool> create();

    /// <summary>
    /// Create the container in the service, passing along metadata for it. If the container already exists, an error is reported.
    /// </summary>
    /// <param name="metadata">User metadata for the container</param>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    pplx::task<bool> create(const cloud_blob_metadata& metadata);

    std::wstring       m_name;
    cloud_blob_client  m_client;
};

/// <summary>
/// Client class representing a range of pages.
/// </summary>
/// <remarks>A page is 512 bytes in size.</remarks>
class page_range
{
public:
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="first">The index of the first page</param>
    /// <param name="last">The index of the last page</param>
    page_range(size_t first, size_t last) : m_first(first), m_last(last) { }

    /// <summary>
    /// Get the total number of pages in the range
    /// </summary>
    /// <returns>The number of pages in the range</returns>
    size_t size() const { return m_last-m_first+1; }

    /// <summary>
    /// Get the first page number in the range
    /// </summary>
    /// <returns>The index of the first page</returns>
    size_t first() const { return m_first; }

    /// <summary>
    /// Get the last page number in the range
    /// </summary>
    /// <returns>The index of the last page</returns>
    size_t last() const { return m_last; }

private:
    friend class cloud_blob;

    size_t m_first;
    size_t m_last;
};

/// <summary>
/// Client class representing a block in a block blob
/// </summary>
class block_info
{
public:
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="first">The index of the first page</param>
    /// <param name="last">The index of the last page</param>
    block_info() : m_size((size_t)-1), m_committed(false) { }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="size">The block size</param>
    /// <param name="id">The id of the block. Note that this is a single-byte string</param>
    /// <remarks>The id cannot be more than 64 bytes long.</remarks>
    block_info(size_t size, const std::vector<unsigned char> &id, bool committed) : m_size(size), m_id(utilities::conversions::to_base64(id)), m_committed(committed) { }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="size">The block size</param>
    /// <param name="id">The id of the block. Note that this is a single-byte string</param>
    /// <remarks>The id cannot be more than 64 bytes long.</remarks>
    block_info(size_t size, const std::string &id, bool committed) : m_size(size), m_id(id), m_committed(committed) { }

    /// <summary>
    /// Get the total number of bytes in the block
    /// </summary>
    /// <returns>The number of bytes in the block</returns>
    size_t size() const { return m_size; }

    /// <summary>
    /// Get the block id, decoded.
    /// </summary>
    /// <returns>The block id</returns>
    std::vector<unsigned char> id() const { return utilities::conversions::from_base64(m_id); }

    /// <summary>
    /// Get the block id, base-64-encoded.
    /// </summary>
    /// <returns>The block id</returns>
    std::string raw_id() const { return m_id; }

    /// <summary>
    /// Is the block a committed or uncommitted block?
    /// </summary>
    bool is_committed() const { return m_committed; }

private:
    bool        m_committed;
    size_t      m_size;
    std::string m_id;
};

/// <summary>
/// Client class representing a block in a block blob
/// </summary>
class put_block_info
{
public:
    enum block_list_options { latest, committed, uncommitted };

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="first">The index of the first page</param>
    /// <param name="last">The index of the last page</param>
    put_block_info() { }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="id">The id of the block. Note that this is a single-byte string</param>
    /// <param name="options">The put option for the block: one of latest, committed, uncommitted</param>
    /// <remarks>The id cannot be more than 64 bytes long.</remarks>
    put_block_info(const std::vector<unsigned char>& id, block_list_options options = latest) : m_options(options), m_id(utilities::conversions::to_base64(id)) { }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="id">The id of the block. Note that this is a single-byte string</param>
    /// <param name="options">The put option for the block: one of latest, committed, uncommitted</param>
    /// <remarks>The id cannot be more than 64 bytes long.</remarks>
    put_block_info(const std::string& id, block_list_options options = latest) : m_options(options), m_id(id) { }

    /// <summary>
    /// Get the put options for the block
    /// </summary>
    /// <returns>The number of bytes in the block</returns>
    size_t options() const { return m_options; }

    /// <summary>
    /// Get the block id, decoded.
    /// </summary>
    /// <returns>The block id</returns>
    std::vector<unsigned char> id() const { return utilities::conversions::from_base64(m_id); }

    /// <summary>
    /// Get the block id, base-64-encoded.
    /// </summary>
    /// <returns>The block id</returns>
    std::string raw_id() const { return m_id; }

private:
    block_list_options m_options;
    std::string        m_id;
};

/// <summary>
/// Client class representing a blob.
/// </summary>
class cloud_blob
{
public:
    enum blob_type          { none, block, page };

    enum block_list_options { all, committed, uncommitted };

    enum lease_status  { unknown, unlocked, locked };
    enum lease_options { acquire, renew, release, break_it };

    /// <summary>
    /// Get the blob type (page, block, unknown)
    /// </summary>
    /// <returns>The name of the blob</returns>
    blob_type get_type() const { return m_properties->m_type; }

    /// <summary>
    /// Get the blob name
    /// </summary>
    /// <returns>The name of the blob</returns>
    std::wstring get_name() const { return m_name; }

    /// <summary>
    /// Get the full blob URI
    /// </summary>
    /// <returns>The URI of the blob</returns>
    http::uri get_uri() const  { return m_container.get_uri().append_path(m_name); }

    /// <summary>
    /// Get the associated blob container
    /// </summary>
    /// <returns>The blob's container instance</returns>
    cloud_blob_container get_container() const { return m_container; }

    /// <summary>
    /// Get the blob's content length, if known.
    /// </summary>
    /// <returns>The blob's content type</returns>
    size_t size() const { return m_properties->m_contentLength; }

    /// <summary>
    /// Get the blob's content type, if known.
    /// </summary>
    /// <returns>The blob's content type</returns>
    std::wstring content_type() const { return m_properties->m_contentType; }

    /// <summary>
    /// Get the blob's content encoding, if known.
    /// </summary>
    /// <returns>The blob's content encoding</returns>
    std::wstring content_encoding() const { return m_properties->m_contentEncoding; }

    /// <summary>
    /// Get the blob's content language, if known.
    /// </summary>
    /// <returns>The blob's content encoding</returns>
    std::wstring content_language() const { return m_properties->m_contentLanguage; }

    /// <summary>
    /// Retrieve any user medatadata that has been defined for the blob.
    /// </summary>
    /// <returns>User metadata for the blob</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<cloud_blob_metadata> get_metadata();

    /// <summary>
    /// Set user medatadata for the blob.
    /// </summary>
    /// <param name="metadata">User metadata for the blob</param>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<bool> set_metadata(const cloud_blob_metadata& metadata);

    /// <summary>
    /// Delete the container from the service.
    /// </summary>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<bool> remove();

#if 0 // Not yet.
    pplx::task<bool> lease(lease_options options);
    pplx::task<bool> snapshot();
#endif

    /// <summary>
    /// Copy a blob within the service.
    /// </summary>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<bool> copy_from(const http::uri& source_blob);
    _AZURESTORAGEIMP pplx::task<bool> copy_from(const cloud_blob& source_blob);
    _AZURESTORAGEIMP pplx::task<bool> copy_from(const http::uri& source_blob, const cloud_blob_metadata& metadata);
    _AZURESTORAGEIMP pplx::task<bool> copy_from(const cloud_blob& source_blob, const cloud_blob_metadata& metadata);

    /// <summary>
    /// Set the blob's properties.
    /// </summary>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<bool> set_properties(const std::wstring& content_type, const std::wstring& content_encoding = L"", const std::wstring& content_language = L"");

    /// <summary>
    /// Get the the properties for the blob.
    /// </summary>
    /// <returns>Success status</returns>
    /// <remarks>This will make a round-trip to the blob service and set the properties of the blob instance</remarks>
    _AZURESTORAGEIMP pplx::task<bool> get_properties();

    /// <summary>
    /// Get the the data contained in the blob.
    /// </summary>
    /// <returns>A vector containing the data from the blob</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<std::vector<unsigned char>> get();

    /// <summary>
    /// Get the the data contained in the blob.
    /// </summary>
    /// <param name="first">The index of the first byte to include in the data</param>
    /// <param name="last">The index of the last byte to include in the data</param>
    /// <returns>A vector containing the data from the blob, in the range specified</returns>
    /// <remarks>This will make a round-trip to the blob service</remarks>
    _AZURESTORAGEIMP pplx::task<std::vector<unsigned char>> get(size_t first, size_t last);

    //
    // BLOCK BLOBS
    //

    /// <summary>
    /// Create or overwrite a block blob, creating an empty blob
    /// </summary>
    pplx::task<bool> put();

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="body_data">The data</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put(std::vector<unsigned char> &&body_data, 
                                          const std::wstring &content_type = std::wstring(L"application/octet-stream"));

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="data">The data</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put(const std::string &data, 
                                          const std::wstring &content_type = std::wstring(L"text/plain"));

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="data">The data</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put(const std::wstring &data, 
                                          const std::wstring &content_type = std::wstring(L"text/plain"));

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="body_data">The data</param>
    /// <param name="metadata">User-provided metadata for the blob</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put(std::vector<unsigned char> &&body_data,
                                          const cloud_blob_metadata& metadata, 
                                          const std::wstring &content_type = std::wstring(L"application/octet-stream"));

    /// <summary>
    /// Get a list of active pages.
    /// </summary>
    /// <returns>The range of pages that are affected</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP pplx::task<std::vector<block_info>> get_block_list(block_list_options options = all);

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="body_data">The data</param>
    /// <param name="id">The block id</param>
    pplx::task<bool> put_block(std::vector<unsigned char> &&body_data, const std::vector<unsigned char>& id)
    {
        return put_block(std::move(body_data), ::utilities::conversions::to_base64(id));
    }

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="body_data">The data</param>
    /// <param name="id">The block id</param>
    _AZURESTORAGEIMP pplx::task<bool> put_block(std::vector<unsigned char> &&body_data, const std::string& id);

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="data">The data</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put_block(const std::string &data, const std::string& id);

    /// <summary>
    /// Create or overwrite a block blob, writing a given amount of data to it
    /// </summary>
    /// <param name="data">The data</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put_block(const std::wstring &data, const std::string& id);

    /// <summary>
    /// Commit a set of blocks that have already been put
    /// </summary>
    /// <param name="body_data">The data</param>
    /// <param name="content_type">The MIME type of the blob</param>
    _AZURESTORAGEIMP pplx::task<bool> put_block_list(const std::vector<put_block_info>& blocks);

    //
    // PAGE BLOBS
    //

    /// <summary>
    /// Create or overwrite a blob
    /// </summary>
    /// <param name="pages">The number of pages to reserve in the blob</param>
    /// <param name="content_type">The MIME type of the blob</param>
    /// <remarks>A page is 512 bytes in size.</remarks>
    _AZURESTORAGEIMP pplx::task<bool> put(size_t pages, 
                                          const std::wstring &content_type = std::wstring(L"application/octet-stream"));

    /// <summary>
    /// Create or overwrite a page blob
    /// </summary>
    /// <param name="pages">The number of pages to reserve in the blob</param>
    /// <param name="metadata">User-provided metadata for the blob</param>
    /// <param name="content_type">The MIME type of the blob</param>
    /// <remarks>A page is 512 bytes in size.</remarks>
    _AZURESTORAGEIMP pplx::task<bool> put(size_t pages, 
                                          const cloud_blob_metadata& metadata, 
                                          const std::wstring &content_type = std::wstring(L"application/octet-stream"));

    /// <summary>
    /// Write a vector of data to a contiguous set of pages in a page blob.
    /// </summary>
    /// <param name="range">The range of pages that are affected</param>
    /// <param name="body_data">The data</param>
    /// <remarks>If the vector is not the same size as the number of pages can hold, it is either truncated
    /// or filled with zeroes. This may require copying of data, so in order to be the most efficient, it is
    /// suggested that the caller make sure that the size is correct. A page is 512 bytes in size.</remarks>
    _AZURESTORAGEIMP pplx::task<bool> put_page(const page_range& range, 
                                               std::vector<unsigned char>&& body_data);

    /// <summary>
    /// Write a vector of data to a contiguous set of pages in a page blob.
    /// </summary>
    /// <param name="first">The index of the first page</param>
    /// <param name="last">The index of the last page</param>
    /// <param name="body_data">The data</param>
    /// <remarks>If the vector is not the same size as the number of pages can hold, it is either truncated
    /// or filled with zeroes. This may require copying of data, so in order to be the most efficient, it is
    /// suggested that the caller make sure that the size is correct. A page is 512 bytes in size.</remarks>
    _AZURESTORAGEIMP pplx::task<bool> put_page(size_t first, 
                                               size_t last, 
                                               std::vector<unsigned char>&& body_data)
    {
        page_range r(first, last);
        return put_page(r, std::move(body_data));
    }

    /// <summary>
    /// Clear a contiguous set of pages in a page blob, freeing up storage capacity for unused portions of
    /// the blob.
    /// </summary>
    /// <param name="range">The range of pages that are affected</param>
    /// <remarks>A page is 512 bytes in size.</remarks>
    _AZURESTORAGEIMP pplx::task<bool> clear_page(const page_range& range);

    /// <summary>
    /// Clear a contiguous set of pages in a page blob, freeing up storage capacity for unused portions of
    /// the blob.
    /// </summary>
    /// <param name="first">The index of the first page</param>
    /// <param name="last">The index of the last page</param>
    /// <remarks>A page is 512 bytes in size.</remarks>
    pplx::task<bool> clear_page(size_t first, size_t last)
    {
        page_range r(first, last);
        return clear_page(r);
    }

    /// <summary>
    /// Get a list of active pages.
    /// </summary>
    /// <returns>The range of pages that are affected</returns>
    /// <remarks></remarks>
    _AZURESTORAGEIMP pplx::task<std::vector<page_range>> get_page_ranges();


    /// <summary>
    /// Default constructor, used by PPL
    /// </summary>
    cloud_blob() { }

    /// <summary>
    /// Constructor
    /// </summary>
    cloud_blob(const std::wstring& name, const cloud_blob_container& container); 

    /// <summary>
    /// Constructor
    /// </summary>
    cloud_blob(blob_type type, const std::wstring& name, const cloud_blob_container& container); 
    
protected:

    std::wstring m_name;
    cloud_blob_container m_container;

private:

    struct _properties
    {
        _properties (blob_type type) : m_type(type), m_contentLength(0), m_lease(unknown) { }

        void _examine_properties(http::http_response response);

        blob_type    m_type;
        lease_status m_lease;

        size_t m_contentLength;

        std::wstring m_contentType;
        std::wstring m_contentEncoding;
        std::wstring m_contentLanguage;
    };

    std::shared_ptr<_properties> m_properties;
};

//
// Inline functions
//
inline pplx::task<cloud_blob_container> cloud_blob_client::create_container(const std::wstring& name)
{
    if ( name == L"$root" )
        throw storage_exception(http::status_codes::NotImplemented, "Root container is not supported.");

    cloud_blob_container cont(name, *this);

    return cont.create().then([cont](pplx::task<bool> result) -> cloud_blob_container
    {
        result.get();
        return cont;
    });
}

inline pplx::task<cloud_blob_container> cloud_blob_client::create_container(const std::wstring& name, const cloud_blob_metadata& metadata)
{
    if ( name == L"$root" )
        throw storage_exception(http::status_codes::NotImplemented, "Root container is not supported.");

    cloud_blob_container cont(name, *this);

    return cont.create(metadata).then([cont](pplx::task<bool> result) -> cloud_blob_container
    {
        result.get();
        return cont;
    });
}
    
inline pplx::task<result_segment<cloud_blob>> cloud_blob_container::list_blobs(size_t maxResults, continuation_token token)
{
    return list_blobs(L"", maxResults, token);
}

inline pplx::task<bool> cloud_blob::put()
{
    std::vector<unsigned char> data;
    return put(std::move(data));
}

#pragma endregion

}} // namespace azure::storage